<?php
/*
Plugin Name: At Reply Mail Notification
Version: 1.1
Author: voidman
Author URI: http://www.voidman.com/
Plugin URI: http://code.google.com/p/at-reply-mail-notification/
Description: This Plugin will make WordPress have the ability to send e-mail notification to the commenters. Commenters can choose whether or not to receive the notifications. Commenters can also cancel notification by clicking the link on the bottom of the e-mail.
*/


if (!class_exists('AtReplyMailNotification')):

class AtReplyMailNotification
{

  var $db = null;

  var $plugin_dir = '';
  var $plugin_url = '';

  var $mail_tpl = array();

  var $lang = null;

  var $error_log = 'error_log.log';
  var $debug = false;
  
  // PHP4 compatible
  function AtReplyMailNotification() {
    register_shutdown_function(array(&$this, '__destruct'));

    $this->__construct();
  }

  function __construct() {
    global $wpdb;

    $this->db = $wpdb;

    $this->plugin_dir = dirname(__FILE__) . '/';  
    $this->plugin_url = WP_PLUGIN_URL . '/' . str_replace(basename( __FILE__), '', plugin_basename(__FILE__));

    $this->load_config();
  }

  function load_config() {
    $config_file = $this->plugin_dir.'config.inc.php';
    file_exists($config_file) && (@include $config_file);

    isset($lang) && (empty($lang) || $lang == 'zh_CN') && $this->lang = $lang;
    is_null($this->lang) ? (defined('WPLANG') && WPLANG == 'zh_CN' && $this->lang = WPLANG) : '';

    isset($error_log) && !empty($error_log) && $this->error_log = $error_log;
    isset($debug) && is_bool($debug) && $this->debug = $debug;
  }

  function __destruct() {
    return true;
  }
  
  function get_current_user_id() {
    global $user_ID;
    return $user_ID;
  }

  function get_file_content($file) {
    $content = '';

    if ( !file_exists($file) )
      return $content;

    if ( function_exists('file_get_contents') )
      $content = @file_get_contents($file);

    if ( $fp = @fopen($file, 'r') ) {
      $content = @fread($fp, filesize($file));
      @fclose($fp);
    }

    return $content;
  }

  function put_file_content($file, $content) {
    if ( empty($content) )
      return false;

    if ( function_exists('file_put_contents') ) {
      @file_put_contents($file, $content, FILE_APPEND);
    } else {
      $fp = @fopen($file, 'a');
		  @fwrite($fp, $content);
		  @fclose($fp);
    }

    return true;
  }

  // Debug
  function log_error() {
    if ( !$this->debug || empty($this->error_log) )
      return false;
    
    $arg_list = func_get_args();
    if (empty($arg_list))
      return false;

    //$data = serialize($arg_list);
    $data = var_export($arg_list, true);
    $data .= "\n\n";
    $log_file = $this->plugin_dir.$this->error_log;

    $this->put_file_content($log_file, $data);
  }

  // To add column called 'comment_notification' (used to identify notification type) to the comments table
  function activate() {
    if (! $this->db->get_results("DESCRIBE {$this->db->comments} comment_notification"))
      $this->db->query("ALTER TABLE {$this->db->comments} ADD COLUMN `comment_notification` TINYINT (1) DEFAULT '-1' NOT NULL");
  }

  function run() {
    add_action('comment_post', array(&$this, 'notify'), 998, 2);
    add_action('wp_set_comment_status', array(&$this, 'notify'), 999, 2);
    
    add_action('wp_footer', array(&$this, 'comment_reply_script'), 999, 2);

    // Admin
    add_action('comment_row_actions', array(&$this, 'admin_comment_row_actions'), 999, 2);
    add_action('admin_print_footer_scripts', array(&$this, 'admin_comment_reply_script'), 9999, 2);
   
    $this->unnotify();
  }

  function get_comments_replyto($comment_content) {
    $comments = $comment_ids = array();
    
    // TODO: match /@([^:\r\n<>]+)(:|\r\n|\n)/
    if ( preg_match_all('/<a(\s|[^<>])+href="#comment-([0-9]+)"(\s|[^>])*>@([^:]+)(:?)<\/a>/', 
      $comment_content, 
      $matches, 
      PREG_SET_ORDER) ) {

      foreach ($matches as $match) {
        $comment_ids[] = $match[2];
      } 
    }
    
    if ($comment_ids) 
      $comments = $this->db->get_results("
        SELECT comment_ID, comment_post_ID, comment_author, comment_author_email, comment_content, c.user_id, comment_notification, p.post_title 
        FROM {$this->db->comments} c 
        INNER JOIN {$this->db->posts} p ON c.comment_post_ID = p.ID 
        WHERE comment_ID IN (" . join(',', $comment_ids) . ") AND comment_approved = 1 AND comment_notification > 0");

    return $comments;
  }

  function can_notify($comment_replyto, $comment) {
    return ($comment_replyto->comment_author_email != $comment->comment_author_email) 
      && ($this->get_current_user_id() != $comment_replyto->user_id)
      && ($comment_replyto->comment_post_ID == $comment->comment_post_ID) 
      && (($comment_replyto->comment_notification == 1 && current_user_can('level_10')) || $comment_replyto->comment_notification == 2);
  }

  function notify($comment_id, $comment_status) { 
    $this->log_error($comment_id, $comment_status);
    
    if (! $comment = get_comment($comment_id))
      return false;

    $this->subscribe_notification($comment);

    if ( !($comment_status == 1 || 'approve' == $comment_status) )
      return false;
    
    $comments_replyto = $this->get_comments_replyto($comment->comment_content);

    $post_permalink = null;
    
    foreach ($comments_replyto as $comment_replyto) {
      if (!is_email($comment_replyto->comment_author_email))
        continue;
      
      if ($this->can_notify($comment_replyto, $comment)) {
        $mail_tpl = $this->get_mail_template();
        
        // Check mail template
        if ( empty($mail_tpl['subject']) || empty($mail_tpl['body']) )
          return false;
        
        if (is_null($post_permalink))
          $post_permalink = get_permalink($comment->comment_post_ID);
        
        // Generate mail
        $mail = $this->get_mail($mail_tpl, $comment_replyto, $comment, $post_permalink);
        
        $this->log_error($mail);

        $this->send_mail($comment_replyto->comment_author_email, $mail);
      }
    }

    return true;
  }
  
  function subscribe_notification($comment) {
    if (isset($_POST['atreplymailnotification'])) {
      $notification = absint($_POST['atreplymailnotification']);

      if ($notification == 1 || $notification == 2) {
        // Administrator
        if (current_user_can('level_10'))
          return false;
 
        // Check post author
        if (! $post = get_post($comment->comment_post_ID) )
          return false;

        if ($post->post_author == $this->get_current_user_id())
          return false;

        $comment_id = $comment->comment_ID;
        $this->db->query("UPDATE {$this->db->comments} SET comment_notification = $notification WHERE comment_ID = $comment_id AND comment_notification = -1");
      }
    }

    return true;
  }

  function unnotify() {
    if (!isset($_GET['unnotify']))
      return false;

    $key = $_GET['unnotify'];

    if (! $key = base64_decode($key)) 
      wp_die('Invalid key.');

    @list($comment_id, $email, $hash) = explode(',', $key);

    $comment_id = intval($comment_id);
    if (! $comment = get_comment($comment_id) )
      wp_die('The comment do not exists.');

    $real_email = $comment->comment_author_email;
    if ($real_email != $email || $this->hash_notification($comment_id, $real_email) != $hash)
      wp_die('Invalid hash.');

    if (! $this->db->query("UPDATE {$this->db->comments} SET comment_notification = 0 WHERE comment_ID = $comment_id AND comment_notification > 0"))
      wp_die('The notification has been turn off.', 'Notice Message');
    else
      wp_die('Unnotify operation succeed.', 'Notice Message');
  }

  function generate_unnotify_key($comment_id, $email) {
    $hash = $this->hash_notification($comment_id, $email);

    return base64_encode($comment_id . ',' . $email . ',' . $hash);
  }

  function get_secret_key() {
    global $wp_default_secret_key;

    $secret_key = '';

    if ( defined('SECRET_KEY') && ('' != SECRET_KEY) && ( $wp_default_secret_key != SECRET_KEY) )
      $secret_key = SECRET_KEY;
    else
      $secret_key = $wp_default_secret_key;

    return $secret_key;
  }

  function hash_notification($comment_id, $email) {
    $secret_key = $this->get_secret_key();

    return hash_hmac('md5', $comment_id.$email, $secret_key);
  }

  function get_mail($mail_tpl, $comment_replyto, $comment, $post_permalink) {
    $mail = $mail_tpl;
    $vars = array(
      'blog_title' => get_option('blogname'),
      'post_title' => htmlspecialchars($comment_replyto->post_title),
    
      'comment_replyto' => $comment_replyto->comment_content,
      'comment_replyto_author' => htmlspecialchars($comment_replyto->comment_author),
      
      'comment' => $comment->comment_content,
      'comment_author' => htmlspecialchars($comment->comment_author),
      'comment_url' => $post_permalink . '#comment-' . $comment->comment_ID,

      'comment_unnotify_url' => $post_permalink . '?unnotify=' . urlencode($this->generate_unnotify_key($comment_replyto->comment_ID, $comment_replyto->comment_author_email)),
      
    );

    foreach ($mail as $key => $tpl) {
      foreach ($vars as $name => $value) {
        if ($name == 'comment_replyto' || $name == 'comment') {
          if ($key == 'body') {
            $value = str_replace("\n", "<br />", $value);
            $value = str_replace('href="#comment-', "target=\"_blank\" href=\"$post_permalink#comment-", $value);
          } elseif ($key == 'body_alt') {
            // Remove html tags
            $value = strip_tags($value);
          }
        }
        $mail[$key] = str_replace('{'.$name.'}', $value, $mail[$key]);
      }
    }

    return $mail;
  }

  function get_mail_template() {
    $basename = 'template.';
    $filename = $this->plugin_dir.$basename;
    if ($this->lang)
      $filename .= $this->lang.'.';

    // Body
    if ( !isset($this->mail_tpl['body']) ){
      $tpl_file = $filename.'html';
      file_exists($tpl_file) || $tpl_file = str_replace($basename, $basename.'default.', $tpl_file);
      $this->mail_tpl['body'] = $this->get_file_content($tpl_file);
    }
    
    // Body alt
    if ( !isset($this->mail_tpl['body_alt']) ){
      $tpl_file = $filename.'txt';
      file_exists($tpl_file) || $tpl_file = str_replace($basename, $basename.'default.', $tpl_file);
      $this->mail_tpl['body_alt'] = $this->get_file_content($tpl_file);
    }

    // Subject
    if ( !isset($this->mail_tpl['subject']) ){
      $this->mail_tpl['subject'] = preg_match("/<title>([^<>]+)<\/title>/", $this->mail_tpl['body'], $matches) ? $matches[1] : '';
    }

    return $this->mail_tpl;
  }


  function send_mail($to, $mail = array()) {
    if ($this->debug)
      return false;

    global $phpmailer;

    if (empty($mail) || !is_array($mail))
      return false;

    $subject = $mail['subject']; 
    $body = $mail['body']; 
    $body_alt = $mail['body_alt'];

    // (Re)create it, if it's gone missing
    if ( !is_object( $phpmailer ) || !is_a( $phpmailer, 'PHPMailer' ) ) {
      require_once ABSPATH . WPINC . '/class-phpmailer.php';
      require_once ABSPATH . WPINC . '/class-smtp.php';
      $phpmailer =& new PHPMailer();
    }

    // Empty out the values that may be set
    $phpmailer->ClearAddresses();
    $phpmailer->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearBCCs();
    $phpmailer->ClearCCs();
    $phpmailer->ClearCustomHeaders();
    $phpmailer->ClearReplyTos();

    // From email and name
    $from_name = get_option('blogname');

    // Get the site domain and get rid of www.
    $sitename = strtolower( $_SERVER['SERVER_NAME'] );
    if ( substr( $sitename, 0, 4 ) == 'www.' ) {
      $sitename = substr( $sitename, 4 );
    }
    $from_email = 'noreply@' . $sitename;

    // Set the from name and email
    $phpmailer->From = $from_email;
    $phpmailer->FromName = $from_name;

    // Set destination address
    $phpmailer->AddAddress( $to ); 

    // Set mail's subject and body
    $phpmailer->Subject = $subject;

    $phpmailer->Body = $body;
    if ($body_alt)
      $phpmailer->AltBody = $body_alt;

    // Set to use PHP's mail()
    $phpmailer->IsMail();

    // Set Content-Type and charset
    $phpmailer->IsHTML(true);
    $phpmailer->CharSet = get_bloginfo('charset');

    do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );

    // Send!
    $result = @$phpmailer->Send();

    return $result;
  }

  function the_at_reply_button($comment_id, $comment_author) {
    echo '<img class="at_reply_btn" src="'.$this->plugin_url.'at-reply.png" alt="reply to this comment" onclick="typeof commentReply !=\'undefined\' && commentReply.replyto('.$comment_id.', \''.$comment_author.'\')"/>';
  }

  function the_notification_switch() {
    $file = $this->plugin_dir.'switch.'.($this->lang ? $this->lang.'.' : '').'html';
    echo $this->get_file_content($file);
  }

  function comment_reply_script() {
    if (is_single() || is_page()) {
      echo '<script type="text/javascript" src="'.$this->plugin_url.'at-reply.js"></script>'.chr(13); 
    }
  }

  /**
   * Admin Page
   */
  function admin_comment_row_actions($actions, $comment) {
    if ( is_array($actions) 
      && isset($actions['reply']) 
      && $this->get_current_user_id() != $comment->user_id ) {

      $html = $actions['reply'];
      $search = array('return false;', 'href="#">');
      $replace = array('commentReply.replyto(\''.$comment->comment_ID.'\',\''.$comment->comment_author.'\');return false;', 'href="#">@');
      $html = str_replace($search, $replace, $html);

      $actions['reply'] .= ' | ' . $html;
    }

    return $actions;
  }

  function admin_comment_reply_script() {
    echo '<script type="text/javascript" src="'.$this->plugin_url.'admin.js"></script>'.chr(13); 
  }

}

if ( !function_exists('the_atreplymailnotification_button') ) :
function the_atreplymailnotification_button() {
  global $atReplyMailNotification, $comment;

  $comment_id = $comment->comment_ID; 
  $comment_author = $comment->comment_author;

  $atReplyMailNotification->the_at_reply_button($comment_id, $comment_author);
}
endif;

if ( !function_exists('the_atreplymailnotification_switch')) :
function the_atreplymailnotification_switch() {
  global $atReplyMailNotification;
  $atReplyMailNotification->the_notification_switch();
}
endif;


// Initialization
$atReplyMailNotification =& new AtReplyMailNotification();

// Installation
register_activation_hook(__FILE__, array(&$atReplyMailNotification, 'activate'));

// Run!
$atReplyMailNotification->run();

endif;
?>